package com.dobbinsoft.iotechn.user.service.open;

import com.alibaba.fastjson.JSONObject;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.dobbinsoft.iotechn.core.constant.Const;
import com.dobbinsoft.iotechn.core.exception.ServiceException;
import com.dobbinsoft.iotechn.core.exception.ThirdPartServiceException;
import com.dobbinsoft.iotechn.core.model.DeveloperSession;
import com.dobbinsoft.iotechn.core.util.GeneratorUtil;
import com.dobbinsoft.iotechn.core.util.SessionUtil;
import com.dobbinsoft.iotechn.core.util.VerifyCodeUtils;
import com.dobbinsoft.iotechn.data.client.SMSClient;
import com.dobbinsoft.iotechn.data.client.SMSResult;
import com.dobbinsoft.iotechn.data.compent.CacheComponent;
import com.dobbinsoft.iotechn.user.api.open.DeveloperService;
import com.dobbinsoft.iotechn.user.constant.UserConst;
import com.dobbinsoft.iotechn.user.domain.DeveloperDO;
import com.dobbinsoft.iotechn.user.dto.DeveloperDTO;
import com.dobbinsoft.iotechn.user.dto.VerifyCodeDTO;
import com.dobbinsoft.iotechn.user.exception.UserExceptionDefinition;
import com.dobbinsoft.iotechn.user.exception.UserServiceException;
import com.dobbinsoft.iotechn.user.mapper.DeveloperMapper;
import com.dobbinsoft.iotechn.core.enums.StatusType;
import com.sun.org.apache.xml.internal.security.utils.Base64;
import org.apache.commons.codec.digest.Md5Crypt;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Date;

/**
 * Description:
 * User: rize
 * Date: 2020/6/7
 * Time: 15:49
 */
@Service("developerService")
public class DeveloperServiceImpl implements DeveloperService {

    @Autowired
    private CacheComponent cacheComponent;

    @Autowired
    private StringRedisTemplate userRedisTemplate;

    @Autowired
    private DeveloperMapper developerMapper;

    @Autowired
    private SMSClient smsClient;

    private static final Logger logger = LoggerFactory.getLogger(DeveloperServiceImpl.class);

    @Override
    public VerifyCodeDTO getVerifyCode() throws ServiceException {
        // 生成随机字串
        String verifyCode = VerifyCodeUtils.generateVerifyCode(4);
        // 唯一标识
        String uuid = GeneratorUtil.genUUId();
        String verifyKey = UserConst.CA_DEVELOPER_VERIFY_CODE + uuid;
        cacheComponent.putRaw(verifyKey, verifyCode, 60);
        // 生成图片
        int w = 111, h = 36;
        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        try {
            VerifyCodeUtils.outputImage(w, h, stream, verifyCode);
            VerifyCodeDTO dto = new VerifyCodeDTO();
            dto.setUuid(uuid);
            dto.setImg(Base64.encode(stream.toByteArray()));
            return dto;
        } catch (Exception e) {
            logger.error("[发送验证码] 异常", e);
            throw new UserServiceException(UserExceptionDefinition.DEVELOPER_GET_VERIFY_CODE_FAILED);
        } finally {
            try {
                stream.close();
            } catch (IOException e) {
            }
        }
    }

    @Override
    @DS(Const.DB_M)
    public DeveloperDTO login(String phone, String password, String verifyCode, String uuid, String ip) throws ServiceException {
        // 1. 校验验证码
        String verifyKey = UserConst.CA_DEVELOPER_VERIFY_CODE + uuid;
        String targetCode = cacheComponent.getRaw(verifyKey);
        if (StringUtils.isEmpty(targetCode)) {
            throw new UserServiceException(UserExceptionDefinition.DEVELOPER_VERIFY_CODE_ERROR);
        }
        if (!targetCode.equalsIgnoreCase(verifyCode)) {
            throw new UserServiceException(UserExceptionDefinition.DEVELOPER_VERIFY_CODE_ERROR);
        }
        // 1.2. 无效化
        cacheComponent.del(verifyKey);
        // 2. 登录验证
        DeveloperDTO developerDTO = developerMapper.login(phone, cryptPwd(phone, password));
        if (developerDTO == null) {
            throw new UserServiceException(UserExceptionDefinition.DEVELOPER_PHONE_OR_PASSWORD_ERROR);
        }
        if (developerDTO.getStatus().intValue() == StatusType.LOCK.getCode()) {
            throw new UserServiceException(UserExceptionDefinition.DEVELOPER_HAS_BEEN_LOCKED);
        }
        // 3. 更新最后登录信息
        DeveloperDO updateDeveloperDO = new DeveloperDO();
        updateDeveloperDO.setId(developerDTO.getId());
        updateDeveloperDO.setGmtLastLogin(new Date());
        updateDeveloperDO.setLastLoginIp(ip);
        developerMapper.updateById(updateDeveloperDO);
        // 4. 生成AccessToken
        String accessToken = developerDTO.getId() + "-" + GeneratorUtil.genUUId();
        userRedisTemplate.opsForValue().set(Const.DEVELOPER_REDIS_PREFIX + accessToken, JSONObject.toJSONString(developerDTO));
        developerDTO.setAccessToken(accessToken);
        return developerDTO;
    }

    @Override
    @DS(Const.DB_M)
    @Transactional(rollbackFor = Exception.class)
    public DeveloperDTO register(String phone, String password, String smsVerifyCode, String ip) throws ServiceException {
        // 1. 校验验证码
        checkSmsVerifyCode(phone, smsVerifyCode);
        // 2. 进行注册
        Date now = new Date();
        DeveloperDO newDeveloperDO = new DeveloperDO();
        newDeveloperDO.setPhone(phone);
        newDeveloperDO.setPassword(cryptPwd(phone, password));
        newDeveloperDO.setStatus(StatusType.ACTIVE.getCode());
        newDeveloperDO.setLastLoginIp(ip);
        newDeveloperDO.setGmtLastLogin(now);
        newDeveloperDO.setGmtUpdate(now);
        newDeveloperDO.setGmtCreate(now);
        developerMapper.insert(newDeveloperDO);
        // 3. 生成AccessToken
        DeveloperDTO developerDTO = new DeveloperDTO();
        BeanUtils.copyProperties(newDeveloperDO, developerDTO);
        String accessToken = newDeveloperDO.getId() + "-" + GeneratorUtil.genUUId();
        userRedisTemplate.opsForValue().set(Const.DEVELOPER_REDIS_PREFIX + accessToken, JSONObject.toJSONString(developerDTO));
        developerDTO.setAccessToken(accessToken);
        return developerDTO;
    }

    @Override
    public String sendSmsVerifyCode(String phone) throws ServiceException {
        String verifyCode = GeneratorUtil.genSixVerifyCode();
        SMSResult res = smsClient.sendRegisterVerify(phone, verifyCode);
        if (res.isSucc()) {
            cacheComponent.putRaw(UserConst.CA_DEVELOPER_SEND_SMS_VERIFY + phone, verifyCode, 300);
            return "ok";
        } else {
            throw new ThirdPartServiceException(res.getMsg(), UserExceptionDefinition.USER_SEND_VERIFY_FAILED.getCode());
        }
    }

    @Override
    @DS(Const.DB_M)
    @Transactional(rollbackFor = Exception.class)
    public String findPassword(String phone, String password, String smsVerifyCode, String ip) throws ServiceException {
        // 1.校验验证码
        this.checkSmsVerifyCode(phone, smsVerifyCode);
        // 2.重置密码
        DeveloperDO developerDO = developerMapper.selectOne(new QueryWrapper<DeveloperDO>().eq("phone", phone));
        if (developerDO == null) {
            throw new UserServiceException(UserExceptionDefinition.DEVELOPER_GET_VERIFY_CODE_FAILED);
        }
        DeveloperDO updateDeveloperDO = new DeveloperDO();
        updateDeveloperDO.setId(developerDO.getId());
        updateDeveloperDO.setPassword(this.cryptPwd(phone, password));
        updateDeveloperDO.setGmtUpdate(new Date());
        if (developerMapper.updateById(updateDeveloperDO) > 0) {
            return "ok";
        }
        throw new UserServiceException(UserExceptionDefinition.USER_UNKNOWN_EXCEPTION);
    }

    @Override
    public String logout(String accessToken) throws ServiceException {
        userRedisTemplate.delete(Const.DEVELOPER_REDIS_PREFIX + accessToken);
        return "ok";
    }

    @Override
    public DeveloperSession info(Long developerId) throws ServiceException {
        return SessionUtil.getDeveloper();
    }

    /**
     * 验证码抽取校验
     * @param phone
     * @param verifyCode
     * @throws ServiceException
     */
    private void checkSmsVerifyCode(String phone, String verifyCode) throws ServiceException {
        String raw = cacheComponent.getRaw(UserConst.CA_DEVELOPER_SEND_SMS_VERIFY + phone);
        if (StringUtils.isEmpty(raw)) {
            throw new UserServiceException(UserExceptionDefinition.DEVELOPER_VERIFY_CODE_NOT_EXIST);
        }
        if (!raw.equals(verifyCode)) {
            throw new UserServiceException(UserExceptionDefinition.DEVELOPER_VERIFY_CODE_ERROR);
        }
    }

    private String cryptPwd(String phone, String password) {
        return Md5Crypt.md5Crypt(password.getBytes(), "$1$" + phone.substring(0, 7));
    }

}
